package com.opennews.openplatform.service;

import com.opennews.openplatform.entity.tables.User;
import com.opennews.openplatform.entity.tables.records.AccountGroupUserRecord;
import com.opennews.openplatform.entity.tables.records.JoinAccountGroupRequestRecord;
import com.opennews.openplatform.entity.tables.records.UserRoleRecord;
import com.opennews.openplatform.jooq.DSLContextWrapper;
import com.opennews.openplatform.jooq.RecordHelper;
import lombok.RequiredArgsConstructor;
import org.jooq.DSLContext;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.opennews.openplatform.entity.Tables.*;

@RequiredArgsConstructor
@Service
public class JoinAccountGroupRequestService {
    private final DSLContext dslContext;
    private final DSLContextWrapper dslContextWrapper;
    private final UserService userService;

    /**
     * Counts received requests by receiver user id.
     *
     * @param receiverUserId: String of receiver user id.
     * @return: Count of requests.
     */
    public int countReceived(String receiverUserId) {
        return dslContextWrapper.count(JOIN_ACCOUNT_GROUP_REQUEST, JOIN_ACCOUNT_GROUP_REQUEST.RECEIVER_ID.eq(receiverUserId));
    }

    /**
     * Queries pending requests for receiver.
     */
    public List<Map<String, Object>> queryReceiving(String receiverUserId, Integer pageSize, Integer pageIndex) {
        // Queries receiver's the request list.
        // Define table aliases
        User ownerAlias = USER.as("owner");
        User senderAlias = USER.as("sender");

        return dslContext.select(
                        JOIN_ACCOUNT_GROUP_REQUEST.ID.as("id"),
                        ownerAlias.USERNAME.as("ownerUsername"),
                        ownerAlias.FULL_NAME.as("ownerFullName"),
                        ownerAlias.AVATAR_FILENAME.as("ownerAvatarFilename"),
                        senderAlias.USERNAME.as("senderUsername"),
                        senderAlias.FULL_NAME.as("senderFullName"),
                        JOIN_ACCOUNT_GROUP_REQUEST.LAST_UPDATED.as("lastUpdated")

                )
                .from(JOIN_ACCOUNT_GROUP_REQUEST)
                .join(ownerAlias).on(JOIN_ACCOUNT_GROUP_REQUEST.OWNER_ID.eq(ownerAlias.ID))
                .join(senderAlias).on(JOIN_ACCOUNT_GROUP_REQUEST.SENDER_ID.eq(senderAlias.ID))
                .where(JOIN_ACCOUNT_GROUP_REQUEST.RECEIVER_ID.eq(receiverUserId))
                .orderBy(JOIN_ACCOUNT_GROUP_REQUEST.LAST_UPDATED.desc())
                .limit(pageSize)
                .offset(pageSize * pageIndex)
                .fetch()
                .map(record -> Map.of(
                        "id", record.get("id"),
                        "owner", Map.of(
                                "username", record.get("ownerUsername").toString(),
                                "fullName", record.get("ownerFullName"),
                                "avatarUrl", userService.getAvatarUrl((String) record.get("ownerAvatarFilename"))
                        ),
                        "sender", Map.of(
                                "username", record.get("senderUsername").toString(),
                                "fullName", record.get("senderFullName")
                        ),
                        "lastUpdated", record.get("lastUpdated")
                ));
    }

    /**
     * Adds new request or updates existing one.
     *
     * @param accountGroupId
     * @param senderId
     * @param receiverId
     * @param roles
     */
    public void addOrUpdate(String accountGroupId, String senderId, String receiverId, List<String> roles) {
        // Queries any existing request even sender is not current user.
        // We just need to make sure only one request is available at same time.
        int requestCount = dslContextWrapper.count(JOIN_ACCOUNT_GROUP_REQUEST, JOIN_ACCOUNT_GROUP_REQUEST.ACCOUNT_GROUP_ID.eq(accountGroupId)
                .and(JOIN_ACCOUNT_GROUP_REQUEST.RECEIVER_ID.eq(receiverId)));

        String joinedRoles = String.join(",", roles);

        if (requestCount == 0) {
            String ownerId = dslContext.selectFrom(ACCOUNT_GROUP_USER)
                    .where(
                            ACCOUNT_GROUP_USER.ACCOUNT_GROUP_ID.eq(accountGroupId)
                                    .and(ACCOUNT_GROUP_USER.IS_ACCOUNT_GROUP_CREATOR.isTrue())
                    )
                    .fetchOne(ACCOUNT_GROUP_USER.USER_ID);

            JoinAccountGroupRequestRecord record = dslContext.newRecord(JOIN_ACCOUNT_GROUP_REQUEST);
            record.setAccountGroupId(accountGroupId);
            record.setOwnerId(ownerId);
            record.setSenderId(senderId);
            record.setReceiverId(receiverId);
            record.setRoles(joinedRoles);
            record.insert();
        } else {
            // Updates roles just in case it has been changed.
            dslContext.update(JOIN_ACCOUNT_GROUP_REQUEST)
                    .set(JOIN_ACCOUNT_GROUP_REQUEST.ROLES, joinedRoles)
                    .set(JOIN_ACCOUNT_GROUP_REQUEST.SENDER_ID, senderId)
                    .where(
                            JOIN_ACCOUNT_GROUP_REQUEST.ACCOUNT_GROUP_ID.eq(accountGroupId)
                                    .and(JOIN_ACCOUNT_GROUP_REQUEST.RECEIVER_ID.eq(receiverId))
                    )
                    .execute();
        }
    }

    /**
     * Request receiver accepts to this request.
     *
     * @param id:         The string of request id.
     * @param receiverId: The string of the user as receiver.
     */
    public void accept(String id, String receiverId) {
        JoinAccountGroupRequestRecord data = dslContext.selectFrom(JOIN_ACCOUNT_GROUP_REQUEST)
                .where(
                        JOIN_ACCOUNT_GROUP_REQUEST.ID.eq(id)
                                .and(JOIN_ACCOUNT_GROUP_REQUEST.RECEIVER_ID.eq(receiverId))
                )
                .fetchOne();

        if (data != null) {
            String[] roleIds = data.getRoles().split(",");

            // Saves relationship between user and account group.
            AccountGroupUserRecord accountGroupUserRecord = dslContext.newRecord(ACCOUNT_GROUP_USER);
            accountGroupUserRecord.setAccountGroupId(data.getAccountGroupId());
            accountGroupUserRecord.setUserId(data.getReceiverId());
            accountGroupUserRecord.setIsAccountGroupCreator(false);
            accountGroupUserRecord.insert();

            // Saves all user roles.
            List<UserRoleRecord> userRoleRecords = new ArrayList<>();

            for (String roleId : roleIds) {
                UserRoleRecord userRoleRecord = dslContext.newRecord(USER_ROLE);
                RecordHelper.setCommonFields(userRoleRecord);
                userRoleRecord.setAccountGroupId(data.getAccountGroupId());
                userRoleRecord.setUserId(data.getReceiverId());
                userRoleRecord.setRoleId(roleId);
                userRoleRecords.add(userRoleRecord);
            }

            if (!userRoleRecords.isEmpty()) {
                dslContext.batchInsert(userRoleRecords).execute();
            }

            // Deletes the completed request.
            deleteReceiving(id, receiverId);
        }
    }

    /**
     * Request receiver rejects to this request.
     *
     * @param id:         The string of request id.
     * @param receiverId: The string of the user as receiver.
     */
    public void reject(String id, String receiverId) {
        // Deletes the completed request.
        deleteReceiving(id, receiverId);
    }

    /**
     * Deletes request based on its id and receiver id.
     *
     * @param id: The string of request id.
     */
    private void deleteReceiving(String id, String receiverId) {
        dslContext.deleteFrom(JOIN_ACCOUNT_GROUP_REQUEST)
                .where(
                        JOIN_ACCOUNT_GROUP_REQUEST.ID.eq(id)
                                .and(JOIN_ACCOUNT_GROUP_REQUEST.RECEIVER_ID.eq(receiverId))
                )
                .execute();
    }
}

